<template>
  <!-- <div> -->
    <div
      :class="['dpro-cascader', { 'dpro-cascader-disabled': disabled }, { 'dpro-cascader-open': _placementVisible }, { 'dpro-cascader-allow-clear': allowClear && _value.length > 0 }]"
      @click="clickTrigger"
    >
      <div class="dpro-cascader-selection" ref="dropdownTrigger">
        <div class="dpro-cascader-selection_rendered">
          <div class="dpro-cascader-selection-selected-value" v-if="_value && _value.length > 0">{{ currentLabel }}</div>
          <div class="dpro-cascader-selection_placeholder" v-else>{{ placeholder }}</div>
        </div>
        <span class="dpro-cascader-arrow">
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" class="icon"><path d="M458.752 656.128 322.304 451.52A64 64 0 0 1 375.68 352h272.768a64 64 0 0 1 53.312 99.52L565.312 656.128a64 64 0 0 1-106.496 0z"/></svg>
        </span>
        <span class="dpro-cascader-selection_clear" @click.stop="clearSelect">
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" class="icon"><path d="M512 0a512 512 0 1 1 0 1024A512 512 0 0 1 512 0zm165.888 280.384-5.568 4.736L512 445.44 351.68 285.12l-5.568-4.736a44.8 44.8 0 0 0-57.792 68.096l160.32 160.256L288.32 669.12a44.8 44.8 0 0 0 57.792 68.096l5.568-4.736L512 572.096 672.32 732.48l5.568 4.736a44.8 44.8 0 0 0 57.792-68.096L575.296 508.736 735.68 348.48a44.8 44.8 0 0 0-57.792-68.096z"/></svg>
        </span>
      </div>
      <!-- 添加绝对定位div元素：解决getPopupContainer没有设置非静态定位属性时的定位问题 -->
      <div ref="placementWrapper" style="position:absolute;left:0;top:0;width:100%;">
        <div
          :class="['dpro-cascader-menus', popupClassName ? popupClassName : '']"
          :style="{ 'top': `${top}px`, 'left': `${left}px` }"
          v-show="_placementVisible"
        >
          <slot name="dropdownRender">
            <div class="dpro-cascader-menus-content">
              <ul class="dpro-cascader-menu" v-for="(option, index) in menuOptionsArr" :key="index">
                <li
                  :class="['dpro-cascader-menu-item', { 'dpro-cascader-menu-item-selected': tempValue && tempValue.includes(item.value), 'dpro-cascader-menu-item-expand': item.children }]"
                  v-for="item in option"
                  :key="item.value"
                  @click.stop="clickItem(item)"
                >
                  {{ item.label }}
                  <span class="dpro-cascader-menu-item-expand-icon" v-if="item.children">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" class="icon"><path d="M441.536 236.672a32 32 0 0 0-54.08 33.728l3.072 4.928L568 509.568l-177.664 239.36a32 32 0 0 0 2.304 40.96l4.288 3.84a32 32 0 0 0 40.96-2.368l3.84-4.288 192-258.56a32 32 0 0 0 2.944-33.28l-3.2-5.12-192-253.44z"/></svg>
                  </span>
                </li>
              </ul>
            </div>
          </slot>
        </div>
      </div>
    </div>
  <!-- </div> -->
</template>

<script>
export default {
  name: 'DproCascader',
  props: {
    /**
     * 指定当前选中的条目
     */
    value: {
      type: Array,
      default: undefined
    },
    /**
     * 可选项数据源
     * array<{value, label, children}>
     */
    options: {
      type: Array,
      default: () => []
    },
    /**
     * 自定义浮层类名
     */
    popupClassName: {
      type: String
    },
    /**
     * 是否禁用
     */
    disabled: {
      type: Boolean,
      default: false
    },
    /**
     * 菜单渲染父节点。默认渲染到 body 上，如果你遇到菜单滚动定位问题，试试修改为滚动的区域，并相对其定位。
     */
    getPopupContainer: {
      type: Function,
      default: () => document.body
    },
    /**
     * 选择框默认文字
     */
    placeholder: {
      type: String,
      default: '请选择'
    },
    /**
     * 支持清除
     */
     allowClear: {
      type: Boolean,
      default: false
    },
    /**
     * 控制浮层显隐
     */
     popupVisible: {
      type: Boolean,
      default: undefined
    },
    /**
     * 当此项为 true 时，点选每级菜单选项值都会发生变化
     */
    changeOnSelect: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      popEl: '',
      placementVisible: false,
      valueNoProp: [], // props无value时内部存储值
      top: 0,
      left: 0,
      width: 0,
      popOffsetTop: 4, // 弹出层纵向偏移量
      tempValue: [] // 缓存当前选中值
    }
  },
  computed: {
    formattedOptions() {
      return this.formatOption(this.options);
    },
    _placementVisible() {
      return this.$props.popupVisible !== undefined ? this.$props.popupVisible : this.placementVisible;
    },
    _value: {
      get: function() {
        return Array.isArray(this.$props.value) ? this.$props.value : this.valueNoProp;
      },
      set: function(val) {
        this.tempValue = [...val];
        val !== this._value && this.$emit('change', val, this.options);
        if(this.$props.value === undefined) {
          this.valueNoProp = val;
        } else {
          this.$emit('input', val);
        }
      }
    },
    menuOptionsArr() {
      // console.log('menuOptionsArr-this.tempValue', this.tempValue);
      let tempArr = this.formattedOptions,
        nextOption = null;
      if(!this.tempValue || this.tempValue.length === 0) return [this.formattedOptions];
      let res = this.tempValue.map((item, index) => {
        let currentItem = tempArr.find(it => it.value === item),
          result = [...tempArr];
        // console.log('currentItem', currentItem);
        tempArr = currentItem?.children || [];
        if(index === this.tempValue.length - 1 && currentItem?.children) {
          nextOption = currentItem.children;
        }
        return result;
      });
      nextOption && res.push(nextOption);

      return res;
    },

    containerNode() {
      return this.getPopupContainer();
    },
    currentLabel() {
      if(!Array.isArray(this.formattedOptions)) return undefined;
      let tempArr = this.formattedOptions,
        labels = this._value.map((item, index) => {
          let currentItem = tempArr.find(it => it.value === item);
          if(!currentItem) return '';
          tempArr = currentItem.children || [];
          return currentItem.label;
        });
      return labels.join(' / ');
    }
  },
  watch: {
    _placementVisible(val) {
      if(val) {
        this.tempValue = [...this._value];
        this.bindBodyClickEvent();
      } else {
        this.tempValue = [];
        // 父组件控制隐藏时移除点击事件监听
        this.removeBodyClickEvent();
      }
    }
  },
  mounted() {
    this.tempValue = [...this._value];
    window.addEventListener('resize', this.resizeHandler, true);
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.resizeHandler, true);
    this.removeBodyClickEvent();
    this.removeNode();
  },
  methods: {
    formatOption(options, level = 1) {
      // console.log('formatOption', level);
      return options.map(item => {
        return {
          ...item,
          level,
          children: Array.isArray(item.children) && item.children.length > 0 ? this.formatOption(item.children, level + 1) : item.children
        }
      })
    },
    // 更新top和left值
    setPosition() {
      let triggerEl = this.$refs.dropdownTrigger;
      if(!triggerEl || !this.popEl) return;
      let clientRect = triggerEl.getBoundingClientRect(),
        placementClientRect = this.popEl.getBoundingClientRect();
      // console.log('clientRect', JSON.stringify(clientRect), 'placementClientRect', JSON.stringify(placementClientRect));
      this.top = clientRect.top + clientRect.height - placementClientRect.top + this.popOffsetTop;
      this.left = clientRect.left - placementClientRect.left;
      this.width = clientRect.width;
      // console.log(`top:${this.top};left:${this.left}`)
      return true;
    },
    appendNode() {
      // console.log('appendNode', this.popEl, this.$refs.placementWrapper);
      if(this.popEl) return;
      let popEl = this.$refs.placementWrapper;
      this.popEl = popEl.parentNode.removeChild(popEl);
      this.containerNode.appendChild(popEl);
    },
    removeNode() {
      // console.log('removeNode', this.popEl);
      if(!this.popEl || !this.containerNode.contains(this.popEl)) return;
      this.containerNode.removeChild(this.popEl);
    },
    async clickTrigger() {
      // console.log('clickTrigger', this._placementVisible);
      if(this.disabled) return;
      this.setVisible(!this._placementVisible);
    },
    clickItem(item) {
      if(item.disabled) return;
      // console.log('clickItem', item, this._value);
      // 设置changeOnSelect为true或选择最后一层级时，选定value；否则仅保存当前选中值，不触发$emit('change')事件
      this.tempValue.splice(item.level - 1, this._value.length - item.level + 1, item.value);
      if(this.changeOnSelect || !Array.isArray(item.children)) {
        this._value = [...this.tempValue];
      }
      
      // 选中最后一层级时关闭选择器
      if(!Array.isArray(item.children)) {
        setTimeout(() => {
          this.setVisible(false);
        }, 120);
      }
    },
    clearSelect() {
      this._value = [];
      this._placementVisible && this.setVisible(false);
    },
    bindBodyClickEvent() {
      document.body.addEventListener('click', this.clickBodyHandler, false);
    },
    removeBodyClickEvent() {
      document.body.removeEventListener('click', this.clickBodyHandler, false);
    },
    clickBodyHandler(e) {
      if(!this._placementVisible) return;
      let composedPath = e.composedPath();
      if(composedPath.includes(this.$refs.placementWrapper) || composedPath.includes(this.$refs.dropdownTrigger)) {
        return;
      }
      this.hidePlacement(17);
    },
    hidePlacement(delay = 500) {
      setTimeout(() => {
        this.setVisible(false);
      }, delay);
    },
    setVisible(visible) {
      // console.log('setVisible', visible);
      // 每次显示时获取最新位置
      if(visible) {
        this.appendNode();
        this.setPosition();
      }

      if(this.$props.popupVisible === undefined) {
        this.placementVisible = visible;
      }
      this.$emit('popupVisibleChange', visible);

    },
    // 页面resize时更新定位
    resizeHandler() {
      this._placementVisible && this.setPosition();
    }
  }
}
</script>

<style lang="less">
@import url(../../styles/variables.less);
.dpro-cascader {
  position: relative;
  color: @text-color;
  font-size: 14px;
  display: inline-block;
  outline: 0;
  list-style: none;
  border-radius: 4px;
}
.dpro-cascader-disabled {
  .dpro-cascader-selection {
    opacity: 0.8;
    cursor: not-allowed;
    border-color: rgba(217, 217, 217, .1);
    &:focus,
    &:hover {
      border-color: rgba(217, 217, 217, .1);
      box-shadow: none;
    }
  }
}
.dpro-cascader-open {
  .dpro-cascader-arrow {
    .icon {
      transform: rotate(180deg);
    }
  }
}
.dpro-cascader-allow-clear {
  .dpro-cascader-selection {
    &:hover {
      .dpro-cascader-arrow {
        opacity: 0;
        display: none;
      }
      .dpro-cascader-selection_clear {
        opacity: 1;
        display: inline-block;
      }
    }
  }
}
.dpro-cascader-selection {
  position: relative;
  display: flex;
  align-items: center;
  width: 180px;
  height: 32px;
  cursor: pointer;
  background-color: @select-dropdown-bg;
  border: 1px solid transparent;
  border-radius: 4px;
  outline: none;
  transition: all .3s cubic-bezier(.645,.045,.355,1);
  user-select: none;
  &:focus,
  &:hover {
    border-color: @primary-color;
    box-shadow: 0 0 0 2px rgba(78, 104, 240, 0.2);
  }
}
.dpro-cascader-selection_rendered {
  position: relative;
  width: 100%;
  padding: 4px 24px 4px 11px;
}
.dpro-cascader-selection-selected-value,
.dpro-cascader-selection_placeholder {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dpro-cascader-selection_placeholder {
  user-select: none;
  opacity: 0.5;
}
.dpro-cascader-arrow,
.dpro-cascader-selection_clear {
  display: inline-block;
  position: absolute;
  top: 50%;
  right: 11px;
  margin-top: -6px;
  color: @text-color-secondary;
  font-size: 12px;
  line-height: 1;
  transform-origin: 50% 50%;
  font-style: normal;
  text-align: center;
  vertical-align: -0.125em;
  user-select: none;
  .icon {
    display: block;
    transition: transform .3s;
  }
}
.dpro-cascader-selection_clear {
  background: @select-dropdown-bg;
  opacity: 0;
  display: none;
  z-index: 1;
  border-radius: 50%;
  cursor: pointer;
  transition: color 0.3s ease, opacity 0.15s ease;
  &:hover {
    color: @text-color;
  }
}
.icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15rem;
  fill: currentColor;
  overflow: hidden;
}
.dpro-cascader-menus {
  box-sizing: border-box;
  position: absolute;
  display: block;
  margin: 0;
  padding: 0;
  font-size: 14px;
  color: @text-color;
  z-index: 1050;
  background-color: rgba(30, 37, 69, 0.4);
  backdrop-filter: blur(3px);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  border-radius: 4px;
}
.dpro-cascader-menus-content {
  white-space: nowrap;
}
.dpro-cascader-menu {
  display: inline-block;
  vertical-align: top;
  margin: 0;
  padding: 4px 0;
  overflow: auto;
  list-style: none;
  outline: none;
  min-width: 111px;
  height: 180px;
  background-color: rgba(0, 0, 0, 0.15);
  backdrop-filter: blur();
  border-right: 1px solid #e8e8e8;
  &:last-child {
    border-right-color: transparent;
  }
}
.dpro-cascader-menu-item {
  user-select: none;
  position: relative;
  display: block;
  padding: 5px 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
  line-height: 22px;
  cursor: pointer;
  transition: background .3s ease;
  &:hover {
    background-color: rgba(0, 0, 0, 0.6);
  }
}
.dpro-cascader-menu-item-selected {
  background-color: rgba(0, 0, 0, 0.6);
  color: @text-color;
}
.dpro-cascader-menu-item-expand {
  padding-right: 24px;
}
.dpro-cascader-menu-item-expand-icon {
  display: inline-flex;
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
}
</style>